// This code is licensed under New BSD Licence. For details see project page at
// http://code.google.com/p/hrissan-cocoa-helpers/source/checkout
#include "base64.h"
#include <algorithm>

namespace rfc
{

static const int BASE64_INPUT = 3;
static const int BASE64_OUTPUT = 4;
const char BASE64_TABLE_FILESYSTEM_COMPATIBLE[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=";
const char BASE64_TABLE_STANDARD[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

const unsigned char FROM_BASE64_TABLE[ 0x100 ] = {
    /*00-07*/ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    /*08-0f*/ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    /*10-17*/ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    /*18-1f*/ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    /*20-27*/ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    /*28-2f*/ 0xFF, 0xFF, 0xFF, 0x3e, 0xFF, 0x3e, 0xFF, 0x3f, //2 = '+', '-' and '/'
    /*30-37*/ 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, //8 = '0'-'7'
    /*38-3f*/ 0x3c, 0x3d, 0xFF, 0xFF, 0xFF, 0x40, 0xFF, 0xFF, //2 = '8'-'9' and '='
    /*40-47*/ 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, //7 = 'A'-'G'
    /*48-4f*/ 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, //8 = 'H'-'O'
    /*50-57*/ 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, //8 = 'P'-'W'
    /*58-5f*/ 0x17, 0x18, 0x19, 0xFF, 0xFF, 0xFF, 0xFF, 0x3f, //3 = 'X'-'Z', '_'
    /*60-67*/ 0xFF, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, //7 = 'a'-'g'
    /*68-6f*/ 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, //8 = 'h'-'o'
    /*70-77*/ 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, //8 = 'p'-'w'
    /*78-7f*/ 0x31, 0x32, 0x33, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,  //3 = 'x'-'z'
    /*80-87*/ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    /*88-8f*/ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    /*90-97*/ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    /*98-9f*/ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    /*a0-a7*/ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    /*a8-af*/ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    /*b0-b7*/ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    /*b8-bf*/ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    /*c0-c7*/ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    /*c8-cf*/ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    /*d0-d7*/ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    /*d8-df*/ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    /*e0-e7*/ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    /*e8-ef*/ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    /*f0-f7*/ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
	/*f8-ff*/ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };

int Base64EncodeGetLength( int size )
{
    // output 4 bytes for every 3 input:
    //                1        2        3
    // 1 = --111111 = 111111--
    // 2 = --11XXXX = ------11 XXXX----
    // 3 = --1111XX =          ----1111 XX------
    // 4 = --111111 =                   --111111

    return (((size + BASE64_INPUT - 1) / BASE64_INPUT) * BASE64_OUTPUT);
}

void to_base64( char * result, const unsigned char * data, int count, const char * base64_table)
{
    const unsigned char * pSrc = data;
    char * pDest = result;
    int remained = count;

    while (remained > 0)
    {
		int dwBlockSize = std::min( remained, BASE64_INPUT );
        unsigned char n1, n2 = 0, n3 = 0, n4 = 0;
        switch (dwBlockSize)
        {
        case 3:
            n4  =  (pSrc[ 2 ] & 0x3f);
            n3  = ((pSrc[ 2 ] & 0xc0) >> 6);
        case 2:
            n3 |= ((pSrc[ 1 ] & 0x0f) << 2);
            n2  = ((pSrc[ 1 ] & 0xf0) >> 4);
        case 1:
            n2 |= ((pSrc[ 0 ] & 0x03) << 4);
            n1  = ((pSrc[ 0 ] & 0xfc) >> 2);
            break;
        }
        pSrc += dwBlockSize;
        remained -= dwBlockSize;

        // Padding...
        switch (dwBlockSize)
        {
        case 1: n3 = 64;
        case 2: n4 = 64;
        case 3:
            break;
        }

        // 4 outputs...
        *pDest++ = base64_table[ n1 ];
        *pDest++ = base64_table[ n2 ];
        *pDest++ = base64_table[ n3 ];
        *pDest++ = base64_table[ n4 ];
    }
}

	void to_base64_fast( char * result, const unsigned char * data, int count, const char * base64_table)
	{
		const unsigned char * pSrc = data;
		char * pDest = result;
		int remained = count;
		
		while (remained >= BASE64_INPUT)
		{
			unsigned all = (pSrc[0] << 16) + (pSrc[1] << 8) + pSrc[2];
			
			pDest[3] = base64_table[ (all) & 0x3F ];
			pDest[2] = base64_table[ (all >> 6) & 0x3F ];
			pDest[1] = base64_table[ (all >> 12) & 0x3F ];
			pDest[0] = base64_table[ (all >> 18) & 0x3F ];
			
			pSrc += BASE64_INPUT;
			remained -= BASE64_INPUT;
			pDest += BASE64_OUTPUT;
		}
		
		if (remained > 0)
		{
			unsigned char n1, n2 = 0, n3 = 0, n4 = 0;
			switch (remained)
			{
				case 3:
					n4  =  (pSrc[ 2 ] & 0x3f);
					n3  = ((pSrc[ 2 ] & 0xc0) >> 6);
				case 2:
					n3 |= ((pSrc[ 1 ] & 0x0f) << 2);
					n2  = ((pSrc[ 1 ] & 0xf0) >> 4);
				case 1:
					n2 |= ((pSrc[ 0 ] & 0x03) << 4);
					n1  = ((pSrc[ 0 ] & 0xfc) >> 2);
					break;
			}
			pSrc += remained;
			//        remained -= dwBlockSize;
			
			// Padding...
			switch (remained)
			{
				case 1: n3 = 64;
				case 2: n4 = 64;
				case 3:
					break;
			}
			
			// 4 outputs...
			*pDest++ = base64_table[ n1 ];
			*pDest++ = base64_table[ n2 ];
			*pDest++ = base64_table[ n3 ];
			*pDest++ = base64_table[ n4 ];
		}
	}
int Base64DecodeGetLength( const char * data, int size )
{
    // output 3 bytes for every 4 input:
    //                1        2        3
    // 1 = --111111 = 111111--
    // 2 = --11XXXX = ------11 XXXX----
    // 3 = --1111XX =          ----1111 XX------
    // 4 = --111111 =                   --111111

	if( size == 0 )
		return 0;
    if (size % BASE64_OUTPUT != 0)
        return -1;
    int max_size = ((size + BASE64_OUTPUT - 1) / BASE64_OUTPUT) * BASE64_INPUT;
	// size is at least BASE64_OUTPUT here and we can subscript safely
	if( FROM_BASE64_TABLE[(unsigned char)(data[size-1])] == 64 )
		max_size -= 1;
	if( FROM_BASE64_TABLE[(unsigned char)(data[size-2])] == 64 )
		max_size -= 1;
	return max_size;
}


bool from_base64( unsigned char * result, const char * str, int count)
{
//	int result_size = Base64DecodeGetLength( value.c_str(), value.size() );
//	if( result_size == -1 ) // error - not base64
//		return false;
    const char * pSrc = str;
    unsigned char * pDest = result;
    int remained = count;

    while (remained > 0)
    {
        // 4 inputs...
        int in1 = (unsigned char)*pSrc++;
        int in2 = (unsigned char)*pSrc++;
        int in3 = (unsigned char)*pSrc++;
        int in4 = (unsigned char)*pSrc++;
        remained -= BASE64_OUTPUT; //4

        // Validate ascii...
//        assert( 0 <= in1 && in1 <= 0x7f );
//        assert( 0 <= in2 && in2 <= 0x7f );
//        assert( 0 <= in1 && in3 <= 0x7f );
//        assert( 0 <= in2 && in4 <= 0x7f );

        // Convert ascii to base16...
        in1 = FROM_BASE64_TABLE[ in1 ];
        in2 = FROM_BASE64_TABLE[ in2 ];
        in3 = FROM_BASE64_TABLE[ in3 ];
        in4 = FROM_BASE64_TABLE[ in4 ];

		int mask = in1 | in2 | in3 | in4;
        // Validate base16...
        if( mask == 0xff )
		{
			return false; // not base64 probably
		}
		int sym1 = ((in1 & 0x3f) << 2) | ((in2 & 0x30) >> 4);
		int sym2 = ((in2 & 0x0f) << 4) | ((in3 & 0x3c) >> 2);
		int sym3 = ((in3 & 0x03) << 6) | (in4 & 0x3f);
		if( remained == 0 ) // last sheet
		{
			if( in1 == 64 || in2 == 64 )
				return false; // wrong padding
			*pDest++ = sym1;
			if( in3 == 64 && in4 != 64 ) // wrong padding
				return false;
			else
			if( in3 != 64 && in4 == 64 ) // 1 padding
			{
				*pDest++ = sym2;
			}
			else
			if( in3 != 64 && in4 != 64 ) // 0 padding
			{
				*pDest++ = sym2;
				*pDest++ = sym3;
			}
			// 2 padding - do nothing
		}
		else
		{
			if( (mask & 64) != 0 ) // padding not in the last sheet
				return false;
			*pDest++ = sym1;
			*pDest++ = sym2;
			*pDest++ = sym3;
		}
    }
    return true;
}

/*	NSString * to_base64( NSData * data, const char * base64_table )
	{
		int len = Base64EncodeGetLength( [data length] );
		char buf[len + 1]; // 0 - terminated
		buf[len] = 0;
		to_base64( buf, reinterpret_cast<const unsigned char *>([data bytes]), [data length], base64_table);
		return [NSString stringWithUTF8String:buf];
	}
	NSData * from_base64( NSString * str )
	{
		const char * in = [str UTF8String];
		int len = strlen(in); // str length returns number unrelated to UTF8
		int result_len = Base64DecodeGetLength( in, len );
		if( result_len == -1 )
			return 0;
		unsigned char buf[result_len];
		if( !from_base64( buf, in, len) )
			return 0;
		return [NSData dataWithBytes:buf length:result_len];
	}*/
	NSString * to_base64( NSData * data, const char * base64_table )
	{
		int len = Base64EncodeGetLength( [data length] );
		NSMutableData * d = [NSMutableData dataWithLength:len + 1];
		char * buf = reinterpret_cast<char *>([d mutableBytes]);
		buf[len] = 0;
		to_base64( buf, reinterpret_cast<const unsigned char *>([data bytes]), [data length], base64_table);
		NSMutableData * d2 = [NSMutableData dataWithLength:len + 1];
		char * buf2 = reinterpret_cast<char *>([d2 mutableBytes]);
		buf2[len] = 0;
		to_base64_fast( buf2, reinterpret_cast<const unsigned char *>([data bytes]), [data length], base64_table);
		if( memcmp(buf, buf2, len) != 0 )
			buf2[len] = 0;
		return [NSString stringWithUTF8String:buf];
	}
	NSData * from_base64( NSString * str )
	{
		const char * in = [str UTF8String];
		int len = strlen(in); // str length returns number unrelated to UTF8
		int result_len = Base64DecodeGetLength( in, len );
		if( result_len == -1 )
			return 0;
		NSMutableData * d = [NSMutableData dataWithLength:result_len];
		unsigned char * buf = reinterpret_cast<unsigned char *>([d mutableBytes]);
		if( !from_base64( buf, in, len) )
			return 0;
		return d;
	}

} // namespace rfc
